#include "mainWindow.h"
#include <algorithm>
#include <QMessageBox>
#include <QMenu>
#include <QFileDialog>
#include <QWindow>
#include <qmessagebox.h>
#include "log.h"
#include "symTable.h"
#include "ChartView.h"
#include "SignalItem.h"
#include "compiler.h"
#include "calculator.h"
#include "bufferAllocator.h"

using namespace QtCharts;

MainWindow::MainWindow(QWidget* parent): QMainWindow(parent)
{
    this->logWindow = new LoggerWindow(this);

    UI_INFO("Start init");

    ui.setupUi(this);
    this->setWindowIcon(QIcon(":/icon/mainIcon"));

    ui.pSignalEditorWidget->setUp();

    this->pSeries = new QLineSeries();
    this->pAxisX = new QValueAxis();
    this->pAxisY = new QValueAxis();

    auto pChart = new QChart();
    pChart->setAnimationOptions(QChart::NoAnimation);
    pChart->setBackgroundVisible();
    pChart->autoFillBackground();
    pChart->addAxis(this->pAxisX, Qt::AlignBottom);
    pChart->addAxis(this->pAxisY, Qt::AlignLeft);
    pChart->legend()->setVisible(false);
    pChart->addSeries(this->pSeries);
    this->pSeries->attachAxis(this->pAxisX);
    this->pSeries->attachAxis(this->pAxisY);
    this->pSeries->setUseOpenGL(true);

    ui.pSignalChart->setChart(pChart);

    ui.pSignalList->setDragDropMode(SignalListWidget::InternalMove);

    this->pMenu = new QMenu(ui.pSignalList);
    this->pMenu->addActions({ui.actNewSig, ui.actDelSig, ui.actImport, ui.actExport, ui.actClrSig});

    ui.pSignalExpress->addAction(ui.actRepChar);

    Calculator_t::getInst().setTotolPoint(ui.pCalNum->text().toInt());

    ui.pCalNum->setValidator(new QRegExpValidator(QRegExp("\\d+")));

    connect(ui.actNewSig, &QAction::triggered, this, &MainWindow::addSignal);
    connect(ui.actDelSig, &QAction::triggered, this, &MainWindow::delSignal);
    connect(ui.actImport, &QAction::triggered, this, &MainWindow::importWorkspace);
    connect(ui.actExport, &QAction::triggered, this, &MainWindow::exportWorkspace);
    connect(ui.actClrSig, &QAction::triggered, this, &MainWindow::clrSignal);
    connect(ui.actRepChar, &QAction::triggered, this, &MainWindow::replaceNonStdChars);

    connect(ui.pbAddSig, &QPushButton::clicked, this, &MainWindow::addSignal);
    connect(ui.pbDelSig, &QPushButton::clicked, this, &MainWindow::delSignal);

    connect(ui.pSignalList, &QListWidget::currentItemChanged, this, &MainWindow::enableExpress);

    connect(ui.pCalculateButton, &QPushButton::clicked, this, &MainWindow::calculateCurSig);
    connect(ui.pSignalList, &QListWidget::itemChanged, this, &MainWindow::itemChanged);
    connect(ui.pSignalList, &QListWidget::currentItemChanged, this,
            &MainWindow::currentItemChanged);
    connect(ui.pSignalList, &QListWidget::customContextMenuRequested, this,
            [this](const QPoint& pos) { this->pMenu->exec(ui.pSignalList->mapToGlobal(pos)); });
    connect(ui.pbLog, &QPushButton::clicked, this->logWindow, &LoggerWindow::show);

#ifdef ANDROID
    Compiler_t::loadExtLibs("../lib");
#else
    Compiler_t::loadExtLibs("./lib");
#endif
    UI_INFO("Init done");
}

MainWindow::~MainWindow()
{
    UI_INFO("Destroy");
}

void MainWindow::importWorkspace(void)
{
    // 这个只能选择已存在的文件
    QString fileName =
        QFileDialog::getOpenFileName(this, tr("select file to load"), "workspace_demo", "*.json");

    if (fileName.isEmpty()) {
        QMessageBox::critical(this, tr("file error"), tr("not select a file."));
        return;
    }

    QFile file(fileName);
    if (not file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QMessageBox::critical(this, tr("file error"), tr("can`t open: %1").arg(fileName));
        file.close();
        return;
    }

    const QJsonDocument& doc = QJsonDocument::fromJson(file.readAll());
    if (not doc.isObject()) { // json无效
        QMessageBox::critical(this, tr("file error"), tr("file format error"));
        file.close();
        return;
    }

    const QJsonObject& root = doc.object();

    auto sigArr = root.find(this->arrayKey);
    auto fs = root.find(this->fsKey);
    auto fsUnit = root.find(this->fsUnitKey);
    auto calPoints = root.find(this->calPointsKey);
    auto end = root.end();

    if (sigArr == end || fs == end || fsUnit == end || calPoints == end
        || false == sigArr.value().isArray() || false == fs.value().isDouble()
        || false == fsUnit.value().isDouble() || false == calPoints.value().isDouble()) {
        QMessageBox::critical(this, tr("file error"), tr("file format error"));
        file.close();
        return;
    }

    ui.pFs->setValue(fs.value().toDouble());
    ui.pFsScale->setCurrentIndex(fsUnit.value().toInt());
    ui.pCalNum->setText(QString::number(calPoints.value().toInt()));
    ui.pSignalList->load(sigArr.value().toArray());

    UI_INFO("load file:%s success", fileName.toStdString().c_str());

    return;
}

void MainWindow::exportWorkspace(void)
{
    // 这个可以选择不存在的文件
    QString fileName =
        QFileDialog::getSaveFileName(this, tr("select file to save"), "workspace_demo", "*.json");

    if (fileName.isEmpty()) {
        QMessageBox::critical(this, tr("file error"), tr("not select a file."));
        return;
    }

    QFile file(fileName);
    if (not file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
        QMessageBox::critical(this, tr("file open fail"), tr("can`t open: %1").arg(fileName));
        file.close();
        return;
    }

    QJsonObject root;

    root[this->fsKey] = ui.pFs->text().toDouble();
    root[this->fsUnitKey] = ui.pFsScale->currentIndex();
    root[this->calPointsKey] = ui.pCalNum->text().toInt();
    root[this->arrayKey] = ui.pSignalList->save();

    QJsonDocument doc;

    doc.setObject(root);
    file.write(doc.toJson());
    file.close();

    UI_INFO("save to file:%s success", fileName.toStdString().c_str());
}

void MainWindow::addSignal(void)
{
    for (int i = 0; i < 32; i++) {
        QString itemName = this->sigName.arg(this->suffix.getFirstZero());
        if (not SigSymTable.has(itemName)) {
            auto newItem = new SignalItem(itemName);

            this->curItemText = itemName;
            ui.pSignalList->addItem(newItem);
            ui.pSignalList->editItem(newItem);
            SigSymTable.insert(itemName, newItem);

            UI_INFO("Add signal: %s", itemName.toStdString().c_str());
            return;
        }
    }
    QMessageBox::critical(this, tr("Name duplicate"),
                          tr("Auto name could only try 32 times, if your workspace have a lot of "
                             "auto-named signal, this problem will happen, but you can try add "
                             "signal again to fix it."));
}

void MainWindow::delSignal(void)
{
    auto itemToDel = ui.pSignalList->takeItem(ui.pSignalList->currentRow());
    // 若删除后当前选择项非法,则重新进入禁止编辑表达式的状态
    if (ui.pSignalList->currentRow() == -1) {
        connect(ui.pSignalList, &QListWidget::currentItemChanged, this, &MainWindow::enableExpress);
        ui.pSignalExpress->setDisabled(true);
    }

    if (itemToDel) {
        SigSymTable.remove(itemToDel->text());
        const QString& name = itemToDel->text();
        QRegExp exp("sig\\d+");

        if (exp.exactMatch(name)) { // 符合自动命名规则
            size_t suffix_num = name.mid(3).toUInt();
            UI_INFO("suffix is:%d", suffix_num);
            this->suffix.set(suffix_num, false);
        }

        delete itemToDel;
    }
}

void MainWindow::clrSignal(void)
{
    ui.pSignalList->clear();
    SigSymTable.clear();
    this->suffix.clear();
}

void MainWindow::enableExpress(void)
{
    ui.pSignalExpress->setEnabled(true);
    disconnect(ui.pSignalList, &QListWidget::currentItemChanged, this, &MainWindow::enableExpress);
}

void MainWindow::currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous)
{
    // 隐藏光标
    ui.pSignalChart->hideCursor();

    // 保存上一个
    if (nullptr != previous) {
        auto pItem = static_cast<SignalItem*>(previous);
        pItem->setSourceCode(ui.pSignalExpress->toPlainText());
        pItem->setFFTMode(ui.cbIsFFTmode->checkState() == Qt::Checked);
    }

    // 载入下一个
    if (nullptr != current) {
        auto pItem = static_cast<SignalItem*>(current);
        const QString& expr = pItem->getSourceCode();
        ui.pSignalExpress->setPlainText(expr);
        ui.cbIsFFTmode->setCheckState(pItem->getFFTMode() ? Qt::Checked : Qt::Unchecked);

        if (not expr.isEmpty())
            this->calculateCurSig();
        else
            this->pSeries->clear();
        this->curItemText = current->text();
    } else {
        UI_INFO("Invalid selection clear curItemText");
        this->curItemText.clear();
        ui.pSignalExpress->clear();
        this->pSeries->clear();
        connect(ui.pSignalList, &QListWidget::currentItemChanged, this, &MainWindow::enableExpress);
        ui.pSignalExpress->setDisabled(true);
    }
}

void MainWindow::itemChanged(QListWidgetItem* item)
{
    if (not this->curItemText.isEmpty()) {
        const QString& newName = item->text();
        if (this->sigNameRule.exactMatch(newName)) { // 新的信号名称符合要求
            UI_INFO("Item changed from: %s to: %s", this->curItemText.toStdString().c_str(),
                    newName.toStdString().c_str());
            SigSymTable.remove(this->curItemText);

            if (SigSymTable.insert(newName, static_cast<SignalItem*>(item))) {
                // 改名之前是自动命名
                if (QRegExp("sig\\d+").exactMatch(this->curItemText)) {
                    size_t suffix_num = this->curItemText.mid(3).toUInt();
                    this->suffix.set(suffix_num, false);
                }

                this->curItemText = newName;
                return;
            } else {
                QMessageBox::critical(this, tr("Name duplicate"),
                                      tr("This name is same as another signal "
                                         "name which already exists."));
            }
        } else {
            QMessageBox::critical(this, tr("Name illegal"),
                                  tr("This name not match naming rules."));
        }

        ui.pSignalList->blockSignals(true);
        item->setText(this->curItemText);
        ui.pSignalList->blockSignals(false);
    }
}

void MainWindow::on_pCalNum_editingFinished(void)
{
    // 这里要屏蔽掉输入框的信号,因为下面的MessageBox会获取输入焦点,所以会再次触发editingFinished
    ui.pCalNum->blockSignals(true);

    int newValue = ui.pCalNum->text().toInt();
    auto& calculator = Calculator_t::getInst();

    if (newValue > this->calPointMax || newValue < this->calPointMin) {
        QMessageBox::critical(this, tr("Calculate point error"),
                              tr("%1 is a illegal value (valid range[%2 - %3])")
                                  .arg(newValue)
                                  .arg(this->calPointMin)
                                  .arg(this->calPointMax));
        newValue = calculator.getTotolPoint();
    } else {
        calculator.setTotolPoint(newValue);
    }

    // 这里究竟是保持当前编辑值还是恢复上一次的正确值,两种策略好像都合理,暂定保持编辑值
    //  ui.pCalNum->setText(QString("%1").arg(newValue));
    ui.pCalNum->blockSignals(false);
}

void MainWindow::replaceNonStdChars(void)
{
    const QString& sourceCode = ui.pSignalExpress->toPlainText();
    const QString& stdSourceCode = SignalItem::replaceNonStdChar(sourceCode);
    ui.pSignalExpress->setText(stdSourceCode);
}

void MainWindow::calculateCurSig(void)
{
    auto curCode = ui.pSignalExpress->toPlainText();

    if (curCode.isEmpty())
        return;

    auto pItem = static_cast<SignalItem*>(ui.pSignalList->currentItem());
    pItem->setSourceCode(curCode); // 将编辑框代码保存至item
    bool isFFTMode = ui.cbIsFFTmode->checkState() == Qt::Checked;
    pItem->setFFTMode(isFFTMode);

    // 获取编译器和计算器的实例
    auto& calculator = Calculator_t::getInst();
    auto& compiler = Compiler_t::getInst();

    // 设置计算器使用的采样率
    BasicType fs = ui.pFs->text().toFloat() * this->fsScale[ui.pFsScale->currentIndex()];
    calculator.setFS(fs);

    size_t calNum = ui.pCalNum->text().toInt();
    calculator.setTotolPoint(calNum);

    // 计算之前先编译当前item,item内部会根据item的源码是否修改选择性编译
    if (true == compiler.compile(pItem)) {
        pSeries->clear();

        size_t memSize = CalculatorConf::getBiggestTypeSize() * calNum;
        // 此处必须为最大类型保留足够内存
        BasicType* res = BufferAllocator::getBuffer<BasicType>(memSize);
        const QString& curSigName = ui.pSignalList->currentItem()->text();

        if (false == calculator.calculate(pItem, res)) { // 计算失败
            BufferAllocator::freeBuffer(res);
            UI_ERROR("calculate sig %s error", curSigName.toStdString().c_str());
            return;
        }

        BasicType factor = isFFTMode ? fs / calNum : 1;
        BasicType maxValue = res[0], minValue = res[0];

        // determine display points by mode
        calNum = isFFTMode ? calNum / 2 : calNum;
        for (uint32_t calPoint = 0; calPoint < calNum; calPoint++) {
            BasicType& curValue = res[calPoint];
            maxValue = std::max(curValue, maxValue);
            minValue = std::min(curValue, minValue);
            this->pSeries->append(calPoint * factor, curValue);
        }

        BufferAllocator::freeBuffer(res);

        this->pSeries->setName(curSigName);
        this->pAxisX->setRange(0, isFFTMode ? fs / 2 : calNum);

        BasicType verticalCenter = (maxValue + minValue) / 2;
        BasicType swing = (maxValue - verticalCenter) * this->swingFactor;

        if (0 == swing) {
            swing = maxValue * this->swingFactor;
        }

        UI_INFO("maxValue: %f, minValue: %f", maxValue, minValue);
        UI_INFO("verticalCenter: %f, swing: %f", verticalCenter, swing);

        this->pAxisY->setRange(minValue - swing, maxValue + swing);
    } else {
        UI_ERROR("Signal compile fail");
        // todo: should give more information to display
        QMessageBox::critical(this, tr("signal compile error"), tr("signal express error"));
    }
}
